/*
 * @Author: xujiawei 
 * @Date: 2021-10-18 15:50:04 
 * @Last Modified by: xujiawei
 * @Last Modified time: 2021-10-18 15:56:45
 * 
 * 用来对spine动画快速做处理
 */

import { Utils } from "./Utils";

export default class SpineHelper {
    /**
     * spine动画开启调试模式
     * @param sp 
     * @param debugBones 
     * @param debugSlots 
     * @param debugMesh 
     */
    static openDebug(sp: sp.Skeleton, debugBones: boolean = true, debugSlots: boolean = true, debugMesh: boolean = true) {
        sp.debugBones = debugBones;
        sp.debugSlots = debugSlots;
        sp.debugMesh = debugMesh;
    }

    /**
     * 隐藏插槽
     * @param sp 
     * @param slotName 
     */
    static hideSlot(sp: sp.Skeleton, slotName: string) {
        if (sp && sp.findSlot(slotName)) {
            sp.findSlot(slotName).color.a = 0;
        }
    }

    /**
     * 显示插槽
     * @param sp 
     * @param slotName 
     */
    static showSlot(sp: sp.Skeleton, slotName: string) {
        if (sp && sp.findSlot(slotName)) {
            sp.findSlot(slotName).color.a = 1;
        }
    }

    /**
     * 播放对应动画
     * @param spine 
     * @param animationName 
     * @param loop 如果是循环 则播放完成一轮便算作完成
     */
    static playAnimation(spine: sp.Skeleton, animationName: string, loop: boolean = false, callback?: Function) {
        spine.setAnimation(0, animationName, loop);
        spine.setCompleteListener((trackEntry: sp.spine.TrackEntry, event) => {
            if (animationName == trackEntry.animation.name) {
                if (callback) callback();
            }
        })
    }

    static addAnimation(spine: sp.Skeleton, animationName: string, loop: boolean = false) {
        spine.addAnimation(0, animationName, loop);
    }

    static playAnimationWithEvent(spine: sp.Skeleton, animationName: string, loop: boolean = false, eventName: string = '', eventCb: Function = null, completeCb) {
        spine.setAnimation(0, animationName, loop);
        spine.setEventListener((trackEntry: sp.spine.TrackEntry, event) => {
            if (trackEntry.animation.name == animationName && event.data.name == eventName) {
                eventCb && eventCb();
            }
        });

        spine.setCompleteListener((trackEntry: sp.spine.TrackEntry, event) => {
            if (animationName == trackEntry.animation.name) {
                completeCb && completeCb();
            }
        })
    }

    /**
     * 动画融合
     * @param spine 
     * @param anim1 
     * @param anim2 
     * @param duration 
     */
    static setAnimationMix(spine: sp.Skeleton, anim1: string, anim2: string, duration: number = 0.2) {
        spine.setMix(anim1, anim2, duration);
        spine.setMix(anim2, anim1, duration);
    }

    /**
     * 通过骨骼挂点
     * @param spine 
     * @param boneName 
     * @param targetNode 
     */
    static generateAttachNodes(spine: sp.Skeleton, boneName: string, targetNode: cc.Node) {
        //@ts-ignore
        let attachUtil = spine.attachUtil;
        let boneNodes = attachUtil.generateAttachedNodes(boneName);
        let boneNode = boneNodes[0];
        if (boneNode) {
            boneNode.addChild(targetNode);
            return targetNode;
        }
        return null;
    }

    /**
     * 通过骨骼销毁挂点
     * @param spine 
     * @param boneName 
     */
    static destroyAttachedNodes(spine: sp.Skeleton, boneName: string) {
        //@ts-ignore
        let attachUtil = spine.attachUtil;
        attachUtil.destroyAttachedNodes(boneName);
    }

    /**
     * 销毁所有挂点
     * @param spine 
     */
    static destroyAllAttachedNodes(spine: sp.Skeleton) {
        //@ts-ignore
        let attachUtil = spine.attachUtil;
        attachUtil.destroyAllAttachedNodes();
    }

    /**
     * 通过外部图片动态替换插槽内的图片部件
     * @param spine 动画组件
     * @param texture 图片资源
     * @param slotName 插槽名字
     * @param targetWidth 要替换部件的宽
     * @param targetHeight 题替换部件的高
     * @param isCopy 是否需要拷贝资源 用于解决同一实例一经替换全部替换的问题
     * @returns 如果拷贝了资源则返回拷贝的具体资源 否则返回null 注意：如果拷贝了资源需要自行销毁
     * @example 需要像这样销毁 cc.assetManager.releaseAsset(skeletonData); skeletonData.destroy(); skeletonData = null;
     */
    static updateRegion(spine: sp.Skeleton, slotName: string, texture: cc.Texture2D, targetWidth: number, targetHeight: number, isCopy: boolean = false): sp.SkeletonData {

        const copyData = isCopy ? this.copySkeletonData(spine) : null;

        // 根据目标宽高处理Texture
        this.scaleTextureSize(texture, targetWidth, targetHeight);

        if (cc.sys.isNative) {
            //@ts-ignore
            let medTex = new middleware.Texture2D();
            medTex.setRealTextureIndex(0);//参数随便设置的没怎么研究
            medTex.setPixelsHigh(texture.height);
            medTex.setPixelsWide(texture.width);
            medTex.setNativeTexture(texture.getImpl());
            if (spine['_nativeSkeleton'])
                spine['_nativeSkeleton'].updateRegion(slotName, medTex);
        } else {
            let runtimeData = spine.skeletonData.getRuntimeData();
            let slotIndex = runtimeData.findSlotIndex(slotName);
            let att = spine.skeletonData.getRuntimeData().defaultSkin.attachments[slotIndex][slotName];
            let region = this.createRegion(texture, targetWidth, targetHeight);
            att.region = region;

            if (att instanceof sp.spine.MeshAttachment) {
                att.updateUVs();
            } else {
                att.setRegion(region)
                att.updateOffset();
            }
        }
        // 如果是缓存模式的话需要更新缓存
        if (spine.isAnimationCached()) {
            spine.invalidAnimationCache();
        }

        return copyData;
    }


    static copySkeletonData(skeleton: sp.Skeleton) {
        // 复制一份skeletonData，换装时防止realtime模式下影响到别的动画
        let animation = skeleton.animation;
        let spData: sp.SkeletonData = skeleton.skeletonData;
        let copy = new sp.SkeletonData();
        cc.js.mixin(copy, spData);
        // @ts-ignore
        copy._uuid = Utils.uuid();
        let textureNames = copy['textureNames'];
        let copyTextureNames = [];
        for (let i = 0; i < textureNames.length; i++) {
            copy.atlasText = copy.atlasText.replace(textureNames[i], 'copy_' + textureNames[i]);
            copyTextureNames.push('copy_' + textureNames[i]);
        }
        copy['textureNames'] = copyTextureNames;
        // @ts-ignore
        copy.init && copy.init();
        skeleton.skeletonData = copy;
        skeleton.animation = animation;
        return copy;
    }

    /**
     * web版通过Texture创建部件
     * @param texture 
     */
    private static createRegion(texture: cc.Texture2D, targetWidth: number, targetHeight: number): sp.spine.TextureAtlasRegion {
        // @ts-ignore
        let skeletonTexture = new sp.SkeletonTexture({ width: texture.width, height: texture.height });
        skeletonTexture.setRealTexture(texture);
        let page = new sp.spine.TextureAtlasPage();
        page.name = texture.name;
        page.uWrap = sp.spine.TextureWrap.ClampToEdge;
        page.vWrap = sp.spine.TextureWrap.ClampToEdge;
        page.texture = skeletonTexture;
        page.texture.setWraps(page.uWrap, page.vWrap);
        page.width = texture.width;
        page.height = texture.height;

        let region = new sp.spine.TextureAtlasRegion();
        region.page = page;
        region.width = texture.width;
        region.height = texture.height;
        region.originalWidth = targetWidth;
        region.originalHeight = targetHeight;

        region.degrees = 0;
        region.rotate = false;
        region.u = 0;
        region.v = 0;
        region.u2 = 1;
        region.v2 = 1;
        region.offsetX = (targetWidth - texture.width) / 2;
        region.offsetY = (targetHeight - texture.height) / 2;
        region.texture = skeletonTexture;

        return region;
    }

    private static scaleTextureSize(texture: cc.Texture2D, width: number, height: number) {

        let sHeight = texture.height;
        let sWidth = texture.width;

        let bWidthScale = true;
        if (sHeight / sWidth > height / width) {
            bWidthScale = false;
        }

        if (bWidthScale) {
            if (sWidth < width) return;

            var scale = sWidth / width;

            texture.width = width;
            texture.height = sHeight / scale;
        } else {

            if (sHeight < height) return;

            let scale = sHeight / height;

            texture.height = height;
            texture.width = sWidth / scale;
        }
    }

    /** 使用内置attackment换装 */
    static changePartial(skeleton: sp.Skeleton, slotName: string, targetSkinName:string, targetAttaName:string) {
        // console.log('change cloth:', slotName, targetSkinName, targetAttaName);
        const slot = skeleton.findSlot(slotName);
        const skeletonData = skeleton.skeletonData.getRuntimeData();
        const skin = skeletonData.findSkin(targetSkinName);
        const slotIndex = skeletonData.findSlotIndex(slotName);
        const atta = skin.getAttachment(slotIndex, targetAttaName);
        if (!slot || !atta) {
            cc.error(slot && atta, "slots: " + slotName + ", attach: " + targetAttaName + " not exists!");
            return;
        }
        slot.setAttachment(atta);
    }
}